/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "utils/leb128.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace ark::leb128::test {

// NOLINTBEGIN(readability-magic-numbers,fuchsia-statically-constructed-objects)

template <class T>
struct TestData {
    T value;
    size_t size;
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    uint8_t data[10U];
};

static std::vector<TestData<uint64_t>> g_unsignedTestData {
    {0x00U, 1U, {0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x7fU, 1U, {0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0xffU, 2U, {0xffU, 0x01U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x2d7fU, 2U, {0xffU, 0x5aU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0xffffU, 3U, {0xffU, 0xffU, 0x03U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x192d7fU, 3U, {0xffU, 0xdaU, 0x64U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x1592d7fU, 4U, {0xffU, 0xdaU, 0xe4U, 0x0aU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x11592d7fU, 5U, {0xffU, 0xdaU, 0xe4U, 0x8aU, 0x01U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0xffffffffU, 5U, {0xffU, 0xffU, 0xffU, 0xffU, 0x0fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x1011592d7fU, 6U, {0xffU, 0xdaU, 0xe4U, 0x8aU, 0x81U, 0x02U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0xc1011592d7fU, 7U, {0xffU, 0xdaU, 0xe4U, 0x8aU, 0x81U, 0x82U, 0x03U, 0x80U, 0x80U, 0x80U}},
    {0x80c1011592d7fU, 8U, {0xffU, 0xdaU, 0xe4U, 0x8aU, 0x81U, 0x82U, 0x83U, 0x04U, 0x80U, 0x80U}},
    {0xffffffffffffffffU, 10U, {0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0x01U}}};

static std::vector<TestData<uint64_t>> g_unsignedPartialDecodingTestData {
    {0xffffffffffffffffU, 10U, {0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0x03U}},
};

static std::vector<TestData<int8_t>> g_signedTestDatA8 {
    {0x00U, 1U, {0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x01U, 1U, {0x01U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-1L, 1U, {0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x40U, 2U, {0xc0U, 0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int8_t>(0x80U), 2U, {0x80U, 0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-0x40L, 1U, {0x40U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}}};

static std::vector<TestData<int16_t>> g_signedTestDatA16 {
    {0x00U, 1U, {0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x0102U, 2U, {0x82U, 0x02U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-1L, 1U, {0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-0x40L, 1U, {0x40U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int16_t>(0x8000U), 3U, {0x80U, 0x80U, 0x7eU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int16_t>(0x4001U), 3U, {0x81U, 0x80U, 0x01U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}}};

static std::vector<TestData<int32_t>> g_signedTestDatA32 {
    {0x00U, 1U, {0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x01020304U, 4U, {0x84U, 0x86U, 0x88U, 0x08U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-1L, 1U, {0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-0x40L, 1U, {0x40U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int32_t>(0x80000000U), 5U, {0x80U, 0x80U, 0x80U, 0x80U, 0x78U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int32_t>(0x40000001U), 5U, {0x81U, 0x80U, 0x80U, 0x80U, 0x04U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}}};

static std::vector<TestData<int8_t>> g_signedPartialDecodingTestDatA8 {
    {1U, 10U, {0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U, 0x8aU}}};

static std::vector<TestData<int16_t>> g_signedPartialDecodingTestDatA16 {
    {-0x3effL, 10U, {0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U, 0x8aU}}};

static std::vector<TestData<int32_t>> g_signedPartialDecodingTestDatA32 {
    {0x5080c101U, 10U, {0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U, 0x8aU}}};

// clang-format off
static std::vector<TestData<int64_t>> g_signedTestDatA64 {
    {0x00U, 1U, {0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x40U, 2U, {0xc0U, 0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {0x7fU, 2U, {0xffU, 0x00U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-1L, 1U, {0x7fU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {static_cast<int64_t>(0x8000000000000000U), 10U,
    {0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x7fU}},
    {0x7000000000000001U, 10U, {0x81U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0xf0U, 0x00U}},
    {0x100000000000000U, 9U, {0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x01U, 0x00U}},
    {-0x40L, 1U, {0x40U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}},
    {-0x1122L, 2U, {0xdeU, 0x5dU, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U}}};

static std::vector<TestData<int64_t>> g_signedPartialDecodingTestDatA64 {
    {0x9101c305080c101U, 10U, {0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U, 0x8aU}},
    {static_cast<int64_t>(0x8000000000000000U), 10U,
    {0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x5fU}}};
// clang-format on

template <class T>
static void TestDecodeUnsigned(const std::vector<TestData<uint64_t>> &data, bool isPartial = false)
{
    for (auto &t : data) {
        std::ostringstream ss;
        ss << "Test unsigned decoding ";
        ss << std::hex << t.value;
        ss << " with sizeof(T) = ";
        ss << sizeof(T);

        constexpr size_t BITWIDTH = std::numeric_limits<T>::digits;

        auto [value, size, is_full] = DecodeUnsigned<T>(t.data);
        EXPECT_EQ(is_full, MinimumBitsToStore(t.value) <= BITWIDTH && !isPartial) << ss.str();
        EXPECT_EQ(size, is_full ? t.size : (BITWIDTH + 6U) / 7U) << ss.str();
        EXPECT_EQ(value, static_cast<T>(t.value)) << ss.str();
    }
}

TEST(Leb128, DecodeUnsigned)
{
    TestDecodeUnsigned<uint8_t>(g_unsignedTestData);
    TestDecodeUnsigned<uint16_t>(g_unsignedTestData);
    TestDecodeUnsigned<uint32_t>(g_unsignedTestData);
    TestDecodeUnsigned<uint64_t>(g_unsignedTestData);
    TestDecodeUnsigned<uint64_t>(g_unsignedPartialDecodingTestData, true);
}

template <class T>
static void TestDecodeSigned(const std::vector<TestData<T>> &data, bool isPartial = false)
{
    for (auto &t : data) {
        std::ostringstream ss;
        ss << "Test signed decoding ";
        ss << std::hex << static_cast<int64_t>(t.value);
        ss << " with sizeof(T) = ";
        ss << sizeof(T);

        constexpr size_t BITWIDTH = std::numeric_limits<std::make_unsigned_t<T>>::digits;

        auto [value, size, is_full] = DecodeSigned<T>(t.data);
        EXPECT_EQ(is_full, !isPartial) << ss.str();
        EXPECT_EQ(size, is_full ? t.size : (BITWIDTH + 6U) / 7U) << ss.str();
        EXPECT_EQ(value, t.value) << ss.str();
    }
}

TEST(Leb128, DecodeSigned)
{
    TestDecodeSigned(g_signedTestDatA8);
    TestDecodeSigned(g_signedTestDatA16);
    TestDecodeSigned(g_signedTestDatA32);
    TestDecodeSigned(g_signedTestDatA64);

    TestDecodeSigned(g_signedPartialDecodingTestDatA8, true);
    TestDecodeSigned(g_signedPartialDecodingTestDatA16, true);
    TestDecodeSigned(g_signedPartialDecodingTestDatA32, true);
    TestDecodeSigned(g_signedPartialDecodingTestDatA64, true);
}

TEST(Leb128, EncodeUnsigned)
{
    for (auto &t : g_unsignedTestData) {
        std::ostringstream ss;
        ss << "Test unsigned encoding ";
        ss << std::hex << t.value;

        std::vector<uint8_t> data(t.size);
        size_t n = EncodeUnsigned(t.value, data.data());
        EXPECT_EQ(n, t.size) << ss.str();
        EXPECT_EQ(UnsignedEncodingSize(t.value), t.size) << ss.str();
        EXPECT_THAT(data, ::testing::ElementsAreArray(t.data, t.size)) << ss.str();
    }
}

template <class T>
void TestEncodeSigned(const std::vector<TestData<T>> &dataVec)
{
    for (auto &t : dataVec) {
        std::ostringstream ss;
        ss << "Test signed encoding ";
        ss << std::hex << static_cast<int64_t>(t.value);

        std::vector<uint8_t> data(t.size);
        size_t n = EncodeSigned(t.value, data.data());
        EXPECT_EQ(n, t.size) << ss.str();
        EXPECT_EQ(SignedEncodingSize(t.value), t.size) << ss.str();
        EXPECT_THAT(data, ::testing::ElementsAreArray(t.data, t.size)) << ss.str();
    }
}

TEST(Leb128, EncodeSigned)
{
    TestEncodeSigned(g_signedTestDatA8);
    TestEncodeSigned(g_signedTestDatA16);
    TestEncodeSigned(g_signedTestDatA32);
    TestEncodeSigned(g_signedTestDatA64);
}

// NOLINTEND(readability-magic-numbers,fuchsia-statically-constructed-objects)

}  // namespace ark::leb128::test
